﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using PriceLibrary.PriceService;


namespace VDataAdapter.Communication
{
    public class VDataMessageBuilderException : Exception
    {
        public VDataMessageBuilderException(string msg, Exception inner) : base(msg, inner) { }
    }

    public class VDataMessageBuilder : IVdataMessageBuilder
    {

        public VDataMessageBuilder(IDictionary<string, PriceLibrary.PriceService.StockBase> basePrices)
        {
            _basePrices = basePrices;
        }
        
        
        
        /// <summary>
        /// Build the message MarketInfo struct included values and properties of new MarketInfo and old MarketInfo
        /// </summary>
        /// <param name="objNew">New market object </param>
        /// <param name="objOld">Old market object </param>
        /// <returns>Return a message string have struct along Market parameter</returns>
        public virtual string BuildMessageMarket(PriceLibrary.PriceService.MarketInfo objNew,
            PriceLibrary.PriceService.MarketInfo objOld)
        {
            
            string strMsgObj = MessageType.MarketInfo.ToString().ToUpper() + "#";
            string strNew = String.Empty;
            string strOld = String.Empty;

            try
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(PriceLibrary.PriceService.MarketInfo));
                foreach (PropertyDescriptor prop in properties)
                {
                    if (prop.PropertyType.ToString() != PropertyTypeRejected)
                    {
                        strNew += GetPropertyMessageMarket(objNew, prop);
                        strOld += GetPropertyMessageMarket(objOld, prop);
                    }
                }
                ///Phân cách giữa Msg mới và Msg cũ bằng dấu #
                strMsgObj += String.Format("{1}#{0}", strOld, strNew);
            }
            catch (Exception ex)
            {
                throw new Exception("Error building a Marketinfo message");
            }
            return strMsgObj;
        }


        string PropertyTypeRejected = "System.Runtime.Serialization.ExtensionDataObject";

        private string GetPropertyMessageMarket(PriceLibrary.PriceService.MarketInfo obj, PropertyDescriptor prop)
        {
            string strMsg = String.Empty;
            if (PropertyIsRejected(prop.PropertyType.ToString()))
                throw new Exception("PropertyTypeRejected exception GetPropertyMessageMarket: System.Runtime.Serialization.ExtensionDataObject");
     
            if (prop.Name == "TradingDate")
            {
                strMsg += "12:00:00" + "|" ?? DBNull.Value.ToString() + "|";

            }  
            else if (prop.Name == "TradingTime")
            {
                var time = int.Parse(prop.GetValue(obj).ToString());
                strMsg += time.ToString("00:00:00") + "|" ?? DBNull.Value.ToString() + "|";
            }
            else if (prop.Name == "FloorCode")
            {
                var time = int.Parse(prop.GetValue(obj).ToString());
                strMsg += time.ToString("00") + "|" ?? DBNull.Value.ToString() + "|";
            }
            else
            {
                strMsg += GetPropertyByDefault(prop, obj);
                //strMsg += prop.GetValue(objNew).ToString().Trim() + "|" ?? DBNull.Value.ToString() + "|";
                //strOld += prop.GetValue(objOld).ToString().Trim() + "|" ?? DBNull.Value.ToString() + "|";
            }
     
            return strMsg;

        }


        List<string> PropertyNameRejected = new List<string>() { "StockNo", "StockSymbol", "TradingDate" };


        private bool PropertyIsRejected(string propertyType)
        {
            return propertyType == PropertyTypeRejected;
        }
        /// <summary>
        /// Remove '|' and '#' from the string
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string RemoveFormatterCharacters(string input)
        {            
            return input.Replace("|", "").Replace("#", "");
        }
        
        /// <summary>
        /// Return 0 if property is null or empty
        /// </summary>
        /// <param name="propertyDescriptor"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        private string GetPropertyByDefault(PropertyDescriptor propertyDescriptor, object property)
        {
            string result = "";
            object value = propertyDescriptor.GetValue(property);

            result = GetValue(value);

            RemoveFormatterCharacters(result);

            return value + "|";
        }

        
        /// <summary>
        /// Return "0" if value is Null or ""
        /// Convert it to string otherwise
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static string GetValue(object value)
        {
            if (value == null)
                return "0";
            else
            if (value.ToString().Equals(""))
                return "0";
            else
                return value.ToString();
        }

        
        private string GetTimeValue(PropertyDescriptor propertyDescriptor, object timeObj)
        {
            int time = int.Parse(propertyDescriptor.GetValue(timeObj).ToString());
            if (time == 0)
                return DateTime.Now.ToString("hh:mm:ss") + "|";
            else
                return time.ToString("00:00:00") + "|" ?? DBNull.Value.ToString() + "|";
        }


        /// <summary>
        /// Build the message StockInfoStatic struct included values and properties of StockInfoStatic
        /// </summary>
        /// <param name="objStatic"></param>
        /// <returns>Return a message string have struct along StockInfoStatic parameter</returns>
        private string BuildMsgStockInfoStatic(PriceLibrary.PriceService.StockInfoStatic objStatic)
        {
            string strMessage = String.Empty;
            try
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(PriceLibrary.PriceService.StockInfoStatic));
                foreach (PropertyDescriptor prop in properties)
                {
                    if (IsNotRejected(prop))
                    {
                        if (prop.Name == "FloorCode")
                        {
                            var FloorCode = int.Parse(prop.GetValue(objStatic).ToString());
                            strMessage += FloorCode.ToString("00") + "|" ?? DBNull.Value.ToString() + "|";
                        }
                        else
                        {
                            strMessage += GetPropertyStr(prop, objStatic);
                            //strMessage += prop.GetValue(objStatic).ToString().Trim() + "|" ?? DBNull.Value.ToString() + "|";
                        }
                        //strMessage += prop.GetValue(objStatic).ToString().Trim() + "|" ?? DBNull.Value.ToString() + "|";
                        //strMessage += prop.Name +"="+prop.GetValue(objStatic).ToString().Trim() + "|\n" ?? DBNull.Value.ToString() + "|\n";
                        //strMessage += GetPropertyStr(prop, objStatic);
                    }
                }
            }
            catch (Exception ex)
            {
                ex.Data["StockInfoStatistics"] = objStatic;
                ex.Data["message"] = strMessage;
                throw ex;
            }
            return strMessage;
        }
         /// <summary>
        /// Return Property Name if value is null or empty
        /// </summary>
        /// <param name="propertyDescriptor"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        private string GetPropertyStr(PropertyDescriptor propertyDescriptor, object property)
        {
            object value = propertyDescriptor.GetValue(property);
            if (value==null || value.ToString().Equals(""))
            {
                value = propertyDescriptor.Name;
            }

            return value.ToString().Trim().Replace("|", "").Replace("#","") + "|";
        }

        private bool IsNotRejected(PropertyDescriptor prop)
        {
            return !PropertyNameRejected.Contains(prop.Name) && prop.PropertyType.ToString() != PropertyTypeRejected;
        }

         

        private string BuildMessageStock(StockInfoStatic baseObj, StockInfoDyns obj)
        {
            string result = String.Empty;

            string prefix = MessageType.Stock.ToString().ToUpper() + "#";

            // string objStr = baseObj.BasicPrice ?? + 

            return result;
        }

        /// <summary>
        /// Build the message Stock struct included values and properties of new Stock and old Stock
        /// </summary>
        /// <param name="objNew"></param>
        /// <param name="objOld"></param>
        /// <returns>Return a message string have struct along Stock parameter</returns>
        public virtual string BuildMessageStock(PriceLibrary.PriceService.StockInfoStatic objStatic,
            PriceLibrary.PriceService.StockInfoDyns objNew, PriceLibrary.PriceService.StockInfoDyns objOld)
        { 
            //string strMsgObj = MessageType.Stock.ToString().ToUpper() + "#";
            //string strDynsMsgNew = String.Empty;
            //string strDynsMsgOld = String.Empty;
            //string strStaticMsg = BuildMsgStockInfoStatic(objStatic);
            //try
            //{
            //    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(PriceLibrary.PriceService.StockInfoDyns));
            //    foreach (PropertyDescriptor prop in properties)
            //    {
            //        if (StockPropertyValidated(prop))
            //        {
            //            if (prop.Name == "Time")
            //            {
            //                strDynsMsgNew += GetTimeValue(prop, objNew);
            //                strDynsMsgOld += GetTimeValue(prop, objOld);
            //            }
            //            else
            //            {
            //                strDynsMsgNew += prop.GetValue(objNew).ToString().Trim() + "|" ?? prop.Name + "|";
            //                strDynsMsgOld += prop.GetValue(objOld).ToString().Trim() + "|" ?? prop.Name + "|";
            //            }
            //            //strDynsMsgNew += prop.Name.Trim() + "|\n" ?? DBNull.Value.ToString() + "|\n";
            //            //strDynsMsgOld += prop.Name.Trim() + "|\n" ?? DBNull.Value.ToString() + "|\n";
            //        }
            //    }
            //    ///Phân cách giữa Msg mới và Msg cũ bằng dấu #
            //    strMsgObj += String.Format("{0}{2}#{0}{1}", strStaticMsg, strDynsMsgOld, strDynsMsgNew);
            //    //_log.WarnFormat("{0}", strMsgObj); 
            //}
            //catch (Exception ex)
            //{
            //    ex.Data["StockInfoStatic"] = objStatic;
            //    ex.Data["new StockInfoDyns"] = objNew;
            //    ex.Data["old StockInfoDyns"] = objOld;
            //    throw ex;
            //}

            //return strMsgObj;
            return StockMessageBuilder.BuildMessage(objStatic, objNew, objOld);

        }

        private bool StockPropertyValidated(PropertyDescriptor prop)
        {
            return 
                !prop.Name.Trim().Equals("ChangePrice") &&
                !prop.Name.Trim().Equals("ChangePercentPrice") &&
                !prop.Name.Trim().ToLower().Equals("StockNo".ToLower()) && 
                !prop.Name.Trim().ToLower().Equals("TradingDate".ToLower()) && 
                prop.PropertyType.ToString() != PropertyTypeRejected;
        }




        /// <summary>
        /// If symbol is one of HOSE, VN30, UPCOM, HNX, HNX30
        /// </summary>
        /// <param name="symbol"></param>
        /// <returns></returns>
        private bool IsMarketInfoSymbol(string symbol)
        {
            List<string> symbolList = new List<string>(new string[] { "HOSE", "VN30", "UPCOM", "HNX", "HNX30" });
            if (symbolList.Contains(symbol))
                return true;
            return false;
        }


        // private DataSource _dataSource;

        private IDictionary<string, PriceLibrary.PriceService.StockBase> _basePrices;

        //public void SetBasePrices(Dictionary<string, PriceLibrary.PriceService.StockBase> basePrices)
        //{
        //    _basePrices = basePrices;
        //}

        /// <summary>
        /// Build message transaction : concat 2 obj new & old
        /// </summary>        
        public virtual string BuildMessageTransaction(PriceLibrary.ChartService.Transaction objNew,
            PriceLibrary.ChartService.Transaction objOld)
        {   
            if (_basePrices == null)
                throw new NullReferenceException("BuildMessageTransaction: DataSource BasePrices is not intiailzed yet");

            string strMsgObj = MessageType.Transaction.ToString().ToUpper() + "#";
            string strNew = String.Empty;
            string strOld = String.Empty;
            string FloorCode = "10";

            if (objNew.MatchType == null)
            {
                objNew.MatchType = "U";
                objOld.MatchType = "U";
            }

            try
            {
                FloorCode = GetFloorCode(objNew, _basePrices);
            }
            catch (KeyNotFoundException ex)
            {
                throw new KeyNotFoundException("Key not found in buildilng message transaction", ex);
                //_log.Error("BuildMessageTransaction error. Not found Symbol  " + objNew.Symbol + " in Datasource BasePrices", ex);
            }

            string actual = FloorCode;

            if (IsMarketInfoSymbol(objNew.Symbol))
                FloorCode = GetFloorCode(objNew.Symbol);
            else ///Get FloorCode From datasource.BasePrices base on Transaction.Symbol
            {
                try
                {
                    var stockstatic = (PriceLibrary.PriceService.StockInfoStatic)_basePrices[objNew.Symbol];
                    FloorCode = stockstatic.FloorCode.ToString();
                }
                catch (Exception ex)
                {
                    throw new Exception("BuildMessageTransaction error. Not found Symbol  " + objNew.Symbol + " in Datasource BasePrices", ex);
                    // _log.Error("BuildMessageTransaction error. Not found Symbol  " + objNew.Symbol + " in Datasource BasePrices", ex);
                }
            }

            try
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(PriceLibrary.ChartService.Transaction));
                foreach (PropertyDescriptor prop in properties)
                {
                    if (prop.PropertyType.ToString() != PropertyTypeRejected)
                    {
                        strNew += GetPropertyTransactionStr(objNew, prop);
                        strOld += GetPropertyTransactionStr(objOld, prop);
                    }
                }
                ///Phân cách giữa Msg mới và Msg cũ bằng dấu #
                strMsgObj += String.Format("{1}{2}#{0}{2}", strOld, strNew, FloorCode);
            }
            catch (Exception ex)
            {
                throw new Exception("Error BuildMessageTransaction", ex);
                //_log.Error("Error BuildMessageTransaction", ex);
            }

            return strMsgObj;
        }

        private string GetPropertyTransactionStr(PriceLibrary.ChartService.Transaction transaction, PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.PropertyType.ToString() != PropertyTypeRejected)
            {
                if (propertyDescriptor.Name == "Time")
                {
                    return GetTimeValue(propertyDescriptor, transaction);
                    //var time = int.Parse(propertyDescriptor.GetValue(transaction).ToString());
                    //return time.ToString("00:00:00") + "|" ?? DBNull.Value.ToString() + "|";
                }
                else if (propertyDescriptor.Name == "FloorCode")
                {
                    var floorCode = int.Parse(propertyDescriptor.GetValue(transaction).ToString());
                    return floorCode.ToString("00") + "|" ?? DBNull.Value.ToString() + "|";
                }
                else if (propertyDescriptor.Name == "LastVol")
                {
                    long lastVol = Convert.ToInt64(transaction.LastVol);
                    return lastVol.ToString() + "|" ?? DBNull.Value.ToString() + "|";
                }
                else
                {
                    return propertyDescriptor.GetValue(transaction).ToString().Trim() + "|" ?? DBNull.Value.ToString() + "|";
                }
            }
            else
                throw new Exception("PropertyTypeRejected, do not know which string to return");
        }

        /// <summary>
        /// Mapped symbol to FloorCode:
        /// HOSE, VN30 >> "10"
        /// UPCOM >> "03"
        /// HNX, HNX30 >> "02"
        /// throw Exception otherwise
        /// </summary>
        /// <param name="symbol"></param>
        /// <returns></returns>
        private string GetFloorCode(string symbol)
        {
            if (symbol.Equals("HOSE", StringComparison.Ordinal) || symbol.Equals("VN30", StringComparison.Ordinal))
                return "10";
            if (symbol.Equals("UPCOM", StringComparison.Ordinal))
                return "03";
            if (symbol.Equals("HNX", StringComparison.Ordinal) || symbol.Equals("HNX30", StringComparison.Ordinal))
                return "02";
            else
                throw new Exception("Invalid Input");
        }


        /// <summary>
        /// Get floor code if is a market info transaction symbol (HOSE, HNX etc)
        /// otherwise get the floor of that symbol from datasource
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="dataSource"></param>
        /// <returns></returns>
        private string GetFloorCode(PriceLibrary.ChartService.Transaction obj, IDictionary<string, PriceLibrary.PriceService.StockBase> basePrices)
        {
            if (IsMarketInfoSymbol(obj.Symbol))
                return GetFloorCode(obj.Symbol);
            else
            {
                if (basePrices.ContainsKey(obj.Symbol))
                {
                    var stockstatic = (PriceLibrary.PriceService.StockInfoStatic)basePrices[obj.Symbol];
                    return stockstatic.FloorCode.ToString();
                }
                else
                    throw new KeyNotFoundException("Not found symbol in DataSource: " + obj.Symbol);
            }
        }





        public string BuildMessagePutThrough(PriceLibrary.PriceService.StockInfoStatic objStatic, PriceLibrary.PriceService.StockInfoDyns objNew, PriceLibrary.PriceService.StockInfoDyns objOld, TransactionType transType)
        {
            string strMsgObj = "PUT#";
            string strDynsMsgNew = String.Empty;
            string strDynsMsgOld = String.Empty;
            try
            {
                //New----------------------------------------------------------
                strDynsMsgNew += BuildMessagePutThrough(objStatic, objNew, transType);

                //Old----------------------------------------------------------
                strDynsMsgOld += BuildMessagePutThrough(objStatic, objOld, transType);

                ///Phân cách giữa Msg mới và Msg cũ bằng dấu #
                strMsgObj += String.Format("{1}#{0}", strDynsMsgOld, strDynsMsgNew);
            }
            catch (Exception ex)
            {
                throw new VDataMessageBuilderException("Error building VDataMessageBuilderException", ex);
                // _log.Error("Error BuildMessageSellPutThrough", ex);
            }

            return strMsgObj;
        }

        protected string BuildMessagePutThrough(PriceLibrary.PriceService.StockInfoStatic objStatic, PriceLibrary.PriceService.StockInfoDyns obj, TransactionType transType)
        {
            string strMsg = String.Empty;
            strMsg += objStatic.FloorCode.ToString("00") + "|";
            strMsg += obj.StockSymbol + "|";

            if (transType == TransactionType.Sell)
            {
                strMsg += obj.Best1Offer + "|";
                strMsg += obj.Best1OfferVolume + "|";
                strMsg += "S|";
            }
            else
                if (transType == TransactionType.Buy)
                {
                    strMsg += obj.Best1Bid + "|";
                    strMsg += obj.Best1BidVolume + "|";
                    strMsg += "B|";
                }
                else
                    throw new InvalidEnumArgumentException("Unknown TransactionType: " + transType.ToString());

            strMsg += obj.Status + "|";
            strMsg += obj.Time + "|";

            return strMsg;
        }

        public string BuildMessageTransactionPutThrough(PriceLibrary.PriceService.Transaction objNew, PriceLibrary.PriceService.Transaction objOld)
        {
            string strMsgObj = "PUTEXEC#";
            string strNew = String.Empty;
            string strOld = String.Empty;
            int FloorCode = 10;
            ///Get FloorCode From datasource.BasePrices base on Transaction.Symbol
            try
            {
                var stockstatic = (PriceLibrary.PriceService.StockInfoStatic)_basePrices[objNew.Symbol];
                FloorCode = stockstatic.FloorCode;
            }
            catch (Exception ex)
            {
                throw new VDataMessageBuilderException("error building transaction put through", ex);                    
            }
            //Build Msg 
            try
            {
                //New----------------------------------------------------------
                strNew += FloorCode.ToString("00") + "|";
                strNew += objNew.Symbol + "|";
                strNew += objNew.Last + "|";
                strNew += objNew.LastVol + "|";
                //strNew += objNew.Time + "|";
                strNew += Convert.ToInt32(objOld.Time).ToString("000000") + "|";
                //Old----------------------------------------------------------                
                strOld += FloorCode + "|";
                strOld += objOld.Symbol + "|";
                strOld += objOld.Last + "|";
                strOld += objOld.LastVol + "|";
                //strOld += objNew.Time + "|";
                strOld += Convert.ToInt32(objOld.Time).ToString("000000") + "|";
                ///Phân cách giữa Msg mới và Msg cũ bằng dấu #
                strMsgObj += String.Format("{1}#{0}", strOld, strNew);
            }
            catch (Exception ex)
            {
                throw new VDataMessageBuilderException("Error building msg transaction put through", ex);
            }
            return strMsgObj;
        }
    }
}
